﻿using System;
using System.Diagnostics.Contracts;

namespace Cloak.Mvvm.Bindings
{
	/// <summary>
	/// A binding applied to a <see cref="BoundModel"/> instance
	/// </summary>
	public sealed class BoundModelBinding : IViewModelBinding
	{
		private readonly Action _raiseIsModifiedPropertyChanged;
		private IViewModelBinding _binding;

		/// <summary>
		/// Initializes a binding with the specified method to raise a changed event for the bound view model's IsModified property
		/// </summary>
		/// <param name="raiseIsModifiedPropertyChanged">The method which raises the changed event for the bound view model's IsModified property</param>
		public BoundModelBinding(Action raiseIsModifiedPropertyChanged)
		{
			Contract.Requires(raiseIsModifiedPropertyChanged != null);

			_raiseIsModifiedPropertyChanged = raiseIsModifiedPropertyChanged;
		}

		#region IViewModelBinding
		/// <summary>
		/// Gets whether the bound view model's data differs from the source data
		/// </summary>
		public bool IsModified
		{
			get { return Binding != null && Binding.IsModified; }
		}

		/// <summary>
		/// Occurs when the value of the <see cref="IsModified"/> property changes
		/// </summary>
		public event EventHandler IsModifiedChanged;

		private void OnIsModifiedChanged(EventArgs e)
		{
			var handler = IsModifiedChanged;

			if(handler != null)
			{
				handler(this, e);
			}

			_raiseIsModifiedPropertyChanged();
		}

		/// <summary>
		/// Reads from the source object and sets values on the bound view model
		/// </summary>
		public void BindFromSource()
		{
			if(Binding != null)
			{
				Binding.BindFromSource();
			}
		}

		/// <summary>
		/// Reads from the bound view model and sets values on the source object
		/// </summary>
		public void BindToSource()
		{
			if(Binding != null)
			{
				Binding.BindToSource();
			}
		}
		#endregion

		/// <summary>
		/// Gets or sets the binding applied to the view model
		/// </summary>
		public IViewModelBinding Binding
		{
			get { return _binding; }
			set
			{
				if(value != _binding)
				{
					if(_binding != null)
					{
						_binding.IsModifiedChanged -= OnBindingIsModifiedChanged;
					}

					if(value != null)
					{
						value.IsModifiedChanged += OnBindingIsModifiedChanged;
					}

					var isModifiedChanged = (value != null && value.IsModified) != (_binding != null && _binding.IsModified);

					_binding = value;

					if(isModifiedChanged)
					{
						OnIsModifiedChanged(EventArgs.Empty);
					}
				}
			}
		}

		private void OnBindingIsModifiedChanged(object sender, EventArgs e)
		{
			OnIsModifiedChanged(EventArgs.Empty);
		}
	}
}